package dev.bessems.usbserial;

import android.hardware.usb.UsbDeviceConnection;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;


import com.felhr.usbserial.PL2303SerialDevice;
import com.felhr.usbserial.UsbSerialDevice;
import com.felhr.usbserial.UsbSerialInterface;

import java.io.IOException;
import java.util.AbstractList;
import java.util.Arrays;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import tw.com.prolific.driver.pl2303g.PL2303GDriver;

public class PL2303UsbSerialPortAdapter implements MethodCallHandler, EventChannel.StreamHandler {

    private final String TAG = PL2303UsbSerialPortAdapter.class.getSimpleName();

    private PL2303GDriver m_SerialDevice;
    private BinaryMessenger m_Messenger;
    private String m_MethodChannelName;
    private EventChannel.EventSink m_EventSink;
    private Handler m_handler;

    private UsbDeviceConnection m_Connection;

    protected SerialReader workerThread;

    PL2303UsbSerialPortAdapter(BinaryMessenger messenger, PL2303GDriver serialDevice, UsbDeviceConnection connection,int interfaceId) {
        m_Messenger = messenger;
        m_SerialDevice = serialDevice;
        m_Connection = connection;
        m_MethodChannelName = "usb_serial/UsbSerialPortAdapter/" + String.valueOf(interfaceId);
        m_handler = new Handler(Looper.getMainLooper());
        final MethodChannel channel = new MethodChannel(m_Messenger, m_MethodChannelName);
        channel.setMethodCallHandler(this);
        final EventChannel eventChannel = new EventChannel(m_Messenger, m_MethodChannelName + "/stream");
        eventChannel.setStreamHandler(this);
    }

    String getMethodChannelName() {
        return m_MethodChannelName;
    }

    private void setPortParameters(int baudRate, int dataBits, int stopBits, int parity) {
        m_SerialDevice.InitByPortSetting(PL2303GDriver.BaudRate.valueOf("B" + baudRate), PL2303GDriver.DataBits.valueOf("D" + dataBits), PL2303GDriver.StopBits.valueOf("S" + stopBits), PL2303GDriver.Parity.values()[parity], PL2303GDriver.FlowControl.OFF);
    }

    private void setFlowControl(int flowControl) {
        //todo
        throw new UnsupportedOperationException();
    }

    private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() {

        @Override
        public void onReceivedData(byte[] arg0) {
            if (m_EventSink != null) {
                m_handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (m_EventSink != null) {
                            m_EventSink.success(arg0);
                        }
                    }
                });
            }
        }

    };

    private Boolean open() {
        if (m_SerialDevice.isConnected()) {
            restartWorkingThread();
            if (workerThread != null) {
                workerThread.setCallback(mCallback);
            }
            return true;
        } else {
            return false;
        }
    }

    private Boolean close() {
        m_SerialDevice.end();
        if (workerThread != null) {
            workerThread.stopThread();
            workerThread = null;
        }
        return true;
    }

    private void write(byte[] data) {
        m_SerialDevice.write(data);
    }

    // return true if the object is to be kept, false if it is to be destroyed.
    public void onMethodCall(MethodCall call, Result result) {

        switch (call.method) {
            case "close":
                result.success(close());
                break;
            case "open":
                result.success(open());
                break;
            case "write":
                write((byte[]) call.argument("data"));
                result.success(true);
                break;

            case "setPortParameters":
                setPortParameters((int) call.argument("baudRate"), (int) call.argument("dataBits"),
                        (int) call.argument("stopBits"), (int) call.argument("parity"));
                result.success(null);
                break;

            case "setFlowControl":
                result.notImplemented();
//                setFlowControl((int) call.argument("flowControl"));
//                result.success(null);
                break;

            case "setDTR": {
                boolean v = call.argument("value");
                m_SerialDevice.setDTR(v);
                if (v == true) {
                    Log.e(TAG, "set DTR to true");
                } else {
                    Log.e(TAG, "set DTR to false");
                }
                result.success(null);
                break;
            }
            case "setRTS": {
                boolean v = call.argument("value");
                m_SerialDevice.setRTS(v);
                result.success(null);
                break;
            }

            default:
                result.notImplemented();
        }
    }

    @Override
    public void onListen(Object o, EventChannel.EventSink eventSink) {
        m_EventSink = eventSink;

    }

    @Override
    public void onCancel(Object o) {
        m_EventSink = null;

    }

    protected void restartWorkingThread() {
        if (workerThread == null || workerThread.isInterrupted()) {
            workerThread = new SerialReader(m_SerialDevice);
            workerThread.start();
            while (!workerThread.isAlive()) {
            }
        }
        // Busy waiting

    }

}


class SerialReader extends AbstractThread {
    private PL2303GDriver serialDevice;
    private UsbSerialInterface.UsbReadCallback callback;


    public SerialReader(PL2303GDriver serialDevice) {
        super();
        this.serialDevice = serialDevice;
    }

    public void setCallback(UsbSerialInterface.UsbReadCallback callback) {
        this.callback = callback;
    }

    byte[] buffer = new byte[20]; // 假设缓冲区大小为 1024 字节

    @Override
    void doRun() {
       
        if (serialDevice.isConnected()) {
            try {
                int bytesRead = serialDevice.read(buffer);
                if (bytesRead > 0) {
                    byte[] data = Arrays.copyOf(buffer, bytesRead);
                    if (callback != null) callback.onReceivedData(data);

                    //System.out.println("Received data: " + new String(data));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}


abstract class AbstractThread extends Thread {
    boolean firstTime = true;
    private volatile boolean keep = true;
    private volatile Thread workingThread;

    void stopThread() {
        keep = false;
        if (this.workingThread != null) {
            this.workingThread.interrupt();
        }
    }

    public final void run() {
        if (!this.keep) {
            return;
        }
        this.workingThread = Thread.currentThread();
        while (this.keep && (!this.workingThread.isInterrupted())) {
            doRun();
        }
    }

    abstract void doRun();
}
